package model;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import math.RMSE;
import data.Data;

public class MF {

	int nuser;
	int maxuid;
	int nitem;
	
	double[] ubias;
	double[] ibias;
	
	double[] ufactor;
	double[] ifactor;	
	
	double avgTrain;
	
	int k;
	
	public MF(List<Data> train) throws IOException {

		 nuser = 93705;
		 maxuid = 95526;
		 nitem = 3561;
			
			ubias = new double[maxuid];
			ibias = new double[nitem];
		 
		double sumTrain = 0.0;
		for(Data data : train){
			byte rat = data.rat;
			sumTrain += rat;
		}			
		
		avgTrain = sumTrain/train.size();
		
		double[] usumbias = new double[maxuid];
		int[] unumbias = new int[maxuid];

		for(int i=0; i<maxuid; i++) {
			usumbias[i] = 0.0;
		}

		for(int i=0; i<maxuid; i++) {
			unumbias[i] = 0;
		}
		
		for(Data data : train){
			int uid = data.uid;
			byte rat = data.rat;
			usumbias[uid-1] += (rat - avgTrain);
			unumbias[uid-1]++;
		}			

		for(int i=0; i<maxuid; i++) {
			ubias[i] = usumbias[i] / (25 + unumbias[i]);
		}
		
		double[] isumbias = new double[nitem];
		int[] inumbias = new int[nitem];
		
		for(Data data : train){
			int uid = data.uid;
			short iid = data.iid;
			byte rat = data.rat;
			isumbias[iid-1] += (rat - avgTrain - usumbias[uid-1]);
			inumbias[iid-1]++;
		}	

		for(int i=0; i<nitem; i++) {
			ibias[i] = isumbias[i] / (10 + inumbias[i]);
		}
	}
	
	public MF(int k, double gamma, double lambda, List<Data> train) throws IOException {

		 nuser = 93705;
		 maxuid = 95526;
		 nitem = 3561;
			
			ubias = new double[maxuid];
			ibias = new double[nitem];
		
		this.k = k;
		
		double sumTrain = 0.0;
		for(Data data : train){
			byte rat = data.rat;
			sumTrain += rat;
		}			
		
		avgTrain = sumTrain/train.size();
		
		ufactor = new double[maxuid * k];
		ifactor = new double[nitem * k];	

		for(int i=0; i<maxuid; i++) {
			ubias[i] = 0.0;
		}

		for(int i=0; i<nitem; i++) {
			ibias[i] = 0.0;
		}
		
		for(int i=0; i<maxuid*k; i++) {
			ufactor[i] = (Math.random()-0.5)/k;
		}
		
		for(int i=0; i<nitem*k; i++) {
			ifactor[i] = (Math.random()-0.5)/k;
		}
		
		//initial error
		System.out.println("Initial training RMSE: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));

		for (int it=1;it<=100;it++){
			Collections.shuffle(train);
			double formerrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
			for(int i=0; i<train.size(); i++) {
				int uid = train.get(i).uid;
				short iid = train.get(i).iid;
				byte rat = train.get(i).rat;

				double pred = 0.0;
				pred += avgTrain;
				pred += ubias[uid-1];
				pred += ibias[iid-1];
				for(int j=0; j<k; j++) {
					pred += ufactor[(uid-1)*k+j]*ifactor[(iid-1)*k+j];
				}
				double err = rat - pred;
				
				ubias[uid-1] =  ubias[uid-1] + gamma*(err-lambda*ubias[uid-1]);
				ibias[iid-1] =  ibias[iid-1] + gamma*(err-lambda*ibias[iid-1]);
				
				for(int j=0; j<k; j++) {
					double newufactor = ufactor[(uid-1)*k+j] + gamma*(err*ifactor[(iid-1)*k+j] - lambda*ufactor[(uid-1)*k+j]);
					double newifactor = ifactor[(iid-1)*k+j] + gamma*(err*ufactor[(uid-1)*k+j] - lambda*ifactor[(iid-1)*k+j]);
					ufactor[(uid-1)*k+j] = newufactor;
					ifactor[(iid-1)*k+j] = newifactor;
				}
			}
			double latterrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
			if (formerrmse - latterrmse < 0.0001){
				System.out.println("Converged at step " + it);
				break;
			}
		}
		
		//error after learning
		System.out.println("Training RMSE after learning: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));
	}

	public MF(List<Data> train, int maxuid2, int maxiid, int k, double gamma, double lambda) {

		nuser = maxuid2;
		maxuid = maxuid2;
		nitem = maxiid;
		
		ubias = new double[maxuid];
		ibias = new double[nitem];
		
		this.k = k;
		
		double sumTrain = 0.0;
		for(Data data : train){
			byte rat = data.rat;
			sumTrain += rat;
		}			
		
		avgTrain = sumTrain/train.size();
		
		ufactor = new double[maxuid * k];
		ifactor = new double[nitem * k];	

		for(int i=0; i<maxuid; i++) {
			ubias[i] = 0.0;
		}

		for(int i=0; i<nitem; i++) {
			ibias[i] = 0.0;
		}
		
		for(int i=0; i<maxuid*k; i++) {
			ufactor[i] = (Math.random()-0.5)/k;
		}
		
		for(int i=0; i<nitem*k; i++) {
			ifactor[i] = (Math.random()-0.5)/k;
		}
		
		//initial error
		System.out.println("Initial training RMSE: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));

		for (int it=1;it<=100;it++){
			Collections.shuffle(train);
			double formerrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
			for(int i=0; i<train.size(); i++) {
				int uid = train.get(i).uid;
				short iid = train.get(i).iid;
				byte rat = train.get(i).rat;

				double pred = 0.0;
				pred += avgTrain;
				pred += ubias[uid-1];
				pred += ibias[iid-1];
				for(int j=0; j<k; j++) {
					pred += ufactor[(uid-1)*k+j]*ifactor[(iid-1)*k+j];
				}
				double err = rat - pred;
				
				ubias[uid-1] =  ubias[uid-1] + gamma*(err-lambda*ubias[uid-1]);
				ibias[iid-1] =  ibias[iid-1] + gamma*(err-lambda*ibias[iid-1]);
				
				for(int j=0; j<k; j++) {
					double newufactor = ufactor[(uid-1)*k+j] + gamma*(err*ifactor[(iid-1)*k+j] - lambda*ufactor[(uid-1)*k+j]);
					double newifactor = ifactor[(iid-1)*k+j] + gamma*(err*ufactor[(uid-1)*k+j] - lambda*ifactor[(iid-1)*k+j]);
					ufactor[(uid-1)*k+j] = newufactor;
					ifactor[(iid-1)*k+j] = newifactor;
				}
			}
			double latterrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
			if (formerrmse - latterrmse < 0.0001){
				System.out.println("Converged at step " + it);
				break;
			}
		}
		
		//error after learning
		System.out.println("Training RMSE after learning: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));
	}

	public double[] getUbias() {
		return ubias;
	}

	public double[] getIbias() {
		return ibias;
	}

	public double[] getUfactor() {
		return ufactor;
	}

	public double[] getIfactor() {
		return ifactor;
	}

	public double getAvgTrain() {
		return avgTrain;
	}

	public int getK() {
		return k;
	}
}
